/*
 *
 * The class belongs to The controller package.
 */
package controllers;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.print.attribute.HashAttributeSet;

import org.apache.commons.collections.map.HashedMap;

import play.mvc.Controller;
import play.mvc.With;

import models.Component;
import models.ComponentUser;
import models.Day;
import models.Event;
import models.Project;
import models.Task;
import models.TaskUserUser;
import models.User;
import models.UserStory;

/**
 * 
 * @author rana ramez BurnDownChart is the chart controller that calculates
 *         everything that is used to draw the chart
 * 
 */
@With(Secure.class)
public class BurnDownChart extends Controller {

	/**
	 * 
	 * @param eventId
	 *            the sprint id
	 * @return the total points included in this sprint
	 */
	public static double totalPoints(final long id) {

		List<Task> result = new LinkedList<Task>();
		Event event = Event.findById(id);
		if (event != null) {
			result = event.getEventTasks();
		}
		return getTasksPoints(result);
	}

	/**
	 * this method takes a list of tasks and calculate the total points for this
	 * tasks
	 * 
	 * @param tasksList
	 * @return
	 */
	public static double getTasksPoints(List<Task> tasksList) {
		double totalPoints = 0;
		for (int i = 0; i < tasksList.size(); i++) {
			totalPoints = totalPoints
					+ tasksList.get(i).initialEffortEstimation;
		}
		return totalPoints;
	}

	/**
	 * this method takes the sprint id and the component id to calculate the
	 * component total points in this sprint
	 * 
	 * @param sprintId
	 * @param componentId
	 * @return
	 */
	public static double componentTotalPoints(long sprintId, long componentId) {
		Component component = Component.findById(componentId);
		LinkedList<Task> tasks = new LinkedList<Task>();
		List<Task> userTasks = new LinkedList<Task>();
		List<Task> task = new LinkedList<Task>();
		List<ComponentUser> users = component.user_components;

		for (int i = 0; i < users.size(); i++) {
			userTasks = TaskUserUser
					.find(
							"select tuu.task from TaskUserUser tuu where tuu.assignee=? and tuu.status=true",
							users.get(i)).fetch();

			task = filterTasksInSprint(userTasks, sprintId);
			while (!userTasks.isEmpty()) {
				userTasks.remove(0);
			}
			while (!task.isEmpty()) {
				tasks.addLast(task.remove(0));
			}
		}
		return getTasksPoints(tasks);
	}

	/**
	 * ONE_HOUR static variable that represent one hour in milliseconds.
	 */
	static final long ONE_HOUR = 60 * 60 * 1000 * 24;

	/**
	 * This method to calculate the duration between two Calendars.
	 * 
	 * @param start
	 *            the start date calendar
	 * @param end
	 *            the end date calendar
	 * @return sprint Duration
	 */
	public static long sprintDuration(final Calendar start, final Calendar end) {

		Date d1 = start.getTime();
		Date d2 = end.getTime();
		long x = d1.getTime();
		long y = d2.getTime();
		return ((y - x) / (ONE_HOUR));
	}

	/**
	 * this method takes all tasks related to a user and the sprint id to filter
	 * thsi tasks and gets the tasks that is included in the sprint
	 * 
	 * @param tasks
	 * @param sprintId
	 * @return
	 */
	public static LinkedList<Task> filterTasksInSprint(List<Task> tasks,
			long sprintId) {
		LinkedList<Task> result = new LinkedList<Task>();
		LinkedList<Task> task = new LinkedList<Task>();
		LinkedList<Task> finalTasks = new LinkedList<Task>();
		Event event = Event.findById(sprintId);
		List<UserStory> stories = Events.getEventUserStories(event);
		for (int i = 0; i < stories.size(); i++) {
			finalTasks = UserStories.getAssociatedTasks((Long) stories.get(i)
					.getEntityId());
			for (int k = 0; k < finalTasks.size(); k++) {
				task.addLast(finalTasks.get(k));
			}
		}
		for (int j = 0; j < tasks.size(); j++) {
			for (int i = 0; i < task.size(); i++) {
				if (tasks.get(j) == task.get(i)) {
					result.addLast(tasks.get(j));
				}
			}
		}

		return result;
	}

	/**
	 * @author Rana Ramez This method takes eventId and gets all tasks
	 *         associated in this event whether from the event_user_stories
	 *         relation and from event_tasks
	 * @param eventId
	 * @return LinkedList<Task>
	 */

	/**
	 * 
	 * @param eventId
	 *            is the sprint id
	 * @return LinkedList<Double> that has each day with its totalEffort done
	 */
	public static LinkedList<Double> getAllDateEffort(final long eventId) {

		long duration = 0;
		Calendar start = new GregorianCalendar();
		Calendar end = new GregorianCalendar();
		Event event = Event.findById(eventId);
		List<Task> tasks = new LinkedList<Task>();
		tasks = event.getEventTasks();
		start = event.startDate;
		end = Calendar.getInstance();
		if (end.compareTo(event.endDate) >= 0) {
			duration = sprintDuration(event.startDate, event.endDate);
		} else {
			duration = sprintDuration(event.startDate, end);
		}
		return getDateEfforts(start, tasks, duration);

	}

	/**
	 * this method takes a list of tasks to get the total points for each day
	 * during the sprint till the current date
	 * 
	 * @param start
	 * @param tasks
	 * @param duration
	 * @return
	 */
	public static LinkedList<Double> getDateEfforts(Calendar start,
			List<Task> tasks, long duration) {
		LinkedList<Double> effort = new LinkedList<Double>();
		double result = 0;
		Calendar m = Calendar.getInstance();

		Calendar c1 = new GregorianCalendar();
		while (duration != 0) {
			result = 0;
			for (int i = 0; i < tasks.size(); i++) {
				for (int j = 0; j < tasks.get(i).dayTasks.size(); j++) {

					c1 = tasks.get(i).dayTasks.get(j).day.date;
					if (c1.get(Calendar.DAY_OF_MONTH) == start
							.get(Calendar.DAY_OF_MONTH)
							&& c1.get(Calendar.MONTH) == start
									.get(Calendar.MONTH)
							&& c1.get(Calendar.YEAR) == start
									.get(Calendar.YEAR)
							&& c1.get(Calendar.DAY_OF_YEAR) == start
									.get(Calendar.DAY_OF_YEAR)
							&& c1.get(Calendar.DAY_OF_WEEK) == start
									.get(Calendar.DAY_OF_WEEK)) {
						result = result
								+ tasks.get(i).dayTasks.get(j).actualEffort;
						break;

					}
				}
			}
			effort.addLast(result);
			start.add(Calendar.DAY_OF_YEAR, 1);
			duration--;
		}
		return effort;

	}

	/**
	 * this methods gets each date effort for the component during the sprint
	 * till the current date
	 * 
	 * @param eventId
	 * @param componentId
	 * @return
	 */
	public static LinkedList<Double> componentDateEffort(long eventId,
			long componentId) {
		Component component = Component.findById(componentId);
		Event event = Event.findById(eventId);
		long duration = 0;
		Calendar start = new GregorianCalendar();
		start = event.startDate;
		Calendar end = new GregorianCalendar();
		List<Task> tasks = new LinkedList<Task>();
		List<Task> task = new LinkedList<Task>();
		List<ComponentUser> users = component.user_components;
		if (!users.isEmpty()) {

			for (int i = 0; i < users.size(); i++) {

				List<Task> userTasks = TaskUserUser
						.find(
								"select tuu.task from TaskUserUser tuu where tuu.assignee=? and tuu.status=?",
								users.get(i), true).fetch();
				if (!userTasks.isEmpty()) {
					task = filterTasksInSprint(userTasks, eventId);
					while (!task.isEmpty()) {
						tasks.add(task.remove(0));
					}
				}
			}
			end = Calendar.getInstance();
			if (end.compareTo(event.endDate) >= 0) {
				duration = sprintDuration(event.startDate, event.endDate);
			} else {
				duration = sprintDuration(event.startDate, end);
			}
		}
		return getDateEfforts(start, tasks, duration);

	}

	/**
	 * This is the Action method for the BurnDown Chart It makes all the
	 * calculations and send them to the view in a linked list.
	 * 
	 * @param id
	 *            the sprint id
	 */

	public static void showChart(final long id) {
		Event event = Event.findById(id);
		if (event != null) {
			Security.checkAccess("View Burn Down Chart", id, Event.class
					.getName());
			Calendar end = Calendar.getInstance();
			LinkedList<Double> averageVelocity = new LinkedList<Double>();
			LinkedList<Double> daysEffort = new LinkedList<Double>();
			if (event != null) {

				if (end.compareTo(event.startDate) >= 0) {

					double totalSprintPoints = totalPoints(id);

					long sprintDuartion = sprintDuration(event.startDate,
							event.endDate);
					if (totalSprintPoints != 0) {
						double x = totalSprintPoints / sprintDuartion;
						double result = totalSprintPoints;
						averageVelocity.addLast(totalSprintPoints);
						for (int i = 0; i < sprintDuartion - 1; i++) {
							result = result - x;
							if (result < 0) {
								break;
							}
							averageVelocity.addLast(result);
						}
						LinkedList<Double> daysOfEffort = getAllDateEffort(id);
						double temp = totalSprintPoints;
						daysEffort.addLast(temp);
						for (int i = 0; i < daysOfEffort.size() - 1; i++) {

							temp = temp - daysOfEffort.get(i);
							daysEffort.addLast(temp);
						}
					}

				}
			}

			render(averageVelocity, daysEffort, event);
		} else {
			forbidden();
		}
	}

	/**
	 * this is the action method that renders the data to the componentChart it
	 * calls the methods taht calculate the total points for the sprint for this
	 * component and calls the componentDateEffort that gets all date efforst
	 * till today for this component and then renders this data to the view
	 * 
	 * @param id
	 *            the sprint id
	 * @param componentId
	 */
	public static void showComponentChart(final long id, final long componentId) {
		Event event = Event.findById(id);
		Component component = Component.findById(componentId);
		if (component != null && event != null) {
			if (component.project != event.project) {
				forbidden();
			}
			if (component.name.equals("Default component")) {
				forbidden();
			}
			if (event.description.equals("default")) {
				forbidden();
			}
			Security.checkAccess("View Burn Down Chart", id, Event.class
					.getName());

			Calendar end = Calendar.getInstance();
			LinkedList<Double> averageVelocity = new LinkedList<Double>();
			LinkedList<Double> daysEffort = new LinkedList<Double>();
			if (event != null && component != null) {
				if (end.compareTo(event.startDate) >= 0) {
					double totalSprintPoints = componentTotalPoints(id,
							componentId);
					long sprintDuartion = sprintDuration(event.startDate,
							event.endDate);
					if (totalSprintPoints != 0) {
						double x = totalSprintPoints / sprintDuartion;
						double result = totalSprintPoints;
						averageVelocity.addLast(totalSprintPoints);
						for (int i = 0; i < sprintDuartion - 1; i++) {
							result = result - x;
							if (result < 0) {
								break;
							}
							averageVelocity.addLast(result);
						}
						LinkedList<Double> daysOfEffort = componentDateEffort(
								id, componentId);

						double temp = totalSprintPoints;
						daysEffort.addLast(temp);
						for (int i = 0; i < daysOfEffort.size() - 1; i++) {
							temp = temp - daysOfEffort.get(i);
							daysEffort.addLast(temp);
						}
					}

				}
			}
			int users = 0;
			if (!component.user_components.isEmpty()) {
				users = component.user_components.size();
			}
			render(averageVelocity, daysEffort, component, event, users);
		}

		else {
			forbidden();
		}

	}
}